package com.freewater.javabase.demo.mutithread.virtualthread;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 * Pinning
 * </p>
 * <p>
 * The virtual thread scheduler mounts virtual threads onto carrier threads. By default, there are as many carrier threads as there are CPU cores. You can tune that count with the jdk.virtualThreadScheduler.parallelism VM option.
 * </p>
 * <p>
 * When a virtual thread executes a blocking operation, it is supposed to be unmounted from its its carrier thread, which can then execute a different virtual thread. However, there are situations where this unmounting is not possible. In some situations, the virtual thread scheduler will compensate by starting another carrier thread. For example, in JDK 21, this happens for many file I/O operations, and when calling Object.wait. You can control the maximum number of carrier threads with the jdk.virtualThreadScheduler.maxPoolSize VM option.
 * </p>
 * <p>
 * A thread is called pinned in either of the two following situations:
 * </p>
 * <p>
 * When executing a synchronized method or block
 * </p>
 * <p>
 * When calling a native method or foreign function
 * </p>
 * Being pinned is not bad in itself. But when a pinned thread blocks, it cannot be unmounted. The carrier thread is blocked, and, in Java 21, no additional carrier thread is started. That leaves fewer carrier threads for running virtual threads.
 * </p>
 * <p>
 * Pinning is harmless if synchronized is used to avoid a race condition in an in-memory operation. However, if there are blocking calls, it would be best to replace synchronized with a ReentrantLock. This is of course only an option if you have control over the source code.
 * </p>
 * <p>
 * To find out whether pinned threads are blocked, start the JVM with one of the options
 * </p>
 * <pre>
 * -Djdk.tracePinnedThreads=short
 * -Djdk.tracePinnedThreads=full
 * </pre>
 * <p>
 * You get a stack trace that shows when a pinned thread blocks:
 * </p>
 * <pre>
 *
 * ...
 * org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) <== monitors:1
 * ...
 * </pre>
 * <p>
 * Note that you get only one warning per pinning location!
 * </p>
 *
 * <p>
 * Alternatively, record with Java Flight Recorder, view with your favorite mission control viewer, and look for VirtualThreadPinned and VirtualThreadSubmitFailed events.
 * </p>
 *
 * <p>
 * The JVM will eventually be implemented so that synchronized methods or blocks no longer lead to pinning. Then you only need to worry about pinning for native code.
 * </p>
 *
 * <p>
 * The following sample program shows pinning in action. We launch a number of virtual threads that sleep in a synchronized method, blocking their carrier threads. A number of virtual threads are added that do no work at all. But they can't be scheduled because the carrier thread pool has been completely exhausted. Note that the problem goes away when you
 * </p>
 *
 * <p>
 * use a ReentrantLock
 * don't use virtual threads
 * </p>
 */
public class PinningDemo {
    // To find out whether pinned threads are blocked, start the JVM with one of the options

    // -Djdk.tracePinnedThreads=short
    // -Djdk.tracePinnedThreads=full

    // You get a stack trace that shows when a pinned thread blocks:
    //
    // ...
    // org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) <== monitors:1
    // ...
    // Note that you get only one warning per pinning location!

    // 运行时添加jvm参数-Djdk.tracePinnedThreads=full来观察pined时被blocked导致的carrier thread无法unmounted异常
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Runnable runnable1 = PinningDemo::block;
        Runnable runnable2 = PinningDemo::reentrantBlock;
        Runnable runnable3 = PinningDemo::noBlock;
        testBlock(runnable1);
        //testBlock(runnable2);
        //testBlock(runnable3);
    }

    private static void testBlock(Runnable runnable) {
        ExecutorService service =
                Executors.newVirtualThreadPerTaskExecutor();
        // Executors.newCachedThreadPool();
        final int TASKS = 20;
        long start = System.nanoTime();
        for (int i = 1; i <= TASKS; i++) {
            service.submit(runnable);
        }
        service.close();
        long end = System.nanoTime();
        System.out.printf("%.2f%n", (end - start) * 1E-9);
    }

    public static synchronized void block() {
        System.out.println("Entering block " + Thread.currentThread());
        LockSupport.parkNanos(1_000_000_000);
        System.out.println("Exiting block " + Thread.currentThread());
    }

    private static final Lock lock = new ReentrantLock();

    public static void reentrantBlock() {
        lock.lock();
        try {
            System.out.println("Entering rblock " + Thread.currentThread());
            LockSupport.parkNanos(1_000_000_000);
            System.out.println("Exiting rblock " + Thread.currentThread());
        } finally {
            lock.unlock();
        }
    }

    public static void noBlock() {
        System.out.println("Entering noblock " + Thread.currentThread());
        LockSupport.parkNanos(1_000_000_000);
        System.out.println("Exiting noblock " + Thread.currentThread());
    }
}
